#include "convertool.h"
#include "qdebug.h"
#include <QTextCodec>

#define DL645解析方式 "配置文件/串口配置和解析方式/命令解析方式.ini"
//相对路径，就在debug的首层文件夹

//converTool::converTool()
//{

//}

//检查输入字符的是不是16进制，去除其中不是的符号
QString 过滤非HEX字符(QString ss)
{
    QString ff;


    for(int i=0;i<ss.length();i++)
    {
        if((ss[i] >= '0') && (ss[i] <= '9'))
            ff.append(ss[i]);
        else if((ss[i] >= 'A') && (ss[i] <= 'F'))
            ff.append(ss[i]);
        else if((ss[i] >= 'a') && (ss[i] <= 'f'))
        {
            ff.append(ss[i]);
        }

    }
    return ff;
}

//每个数逆序
QByteArray 只逆序(QString hexStr)
{
    hexStr=过滤非HEX字符(hexStr);
    QByteArray arr=HEX字符串转HEX数组(hexStr);
    QByteArray array;

    for(int i=0;i<arr.length();i++)
    {
        array.prepend(arr[i]);//每次加到最前面，就可以逆序了
    }
    return array;
}



//字符串转Hex(QByteArray)类型
QByteArray HEX字符串转HEX数组(QString hexStr)
{
    QByteArray senddata;
    int len = hexStr.length();

    //长度是偶数，直接转，长度是奇数，最后一个字符前面加个'0'
    if(len%2==0)
    {
        for(int i = 0; i < len; i+=2 )
        {   //拼接每一个转换后的数字
            senddata.append(两HEX字符转1字节数(hexStr[i].toLatin1(),hexStr[i+1].toLatin1()));
        }
    }

    else
    {
        for(int i = 0; i < len; i+=2 )
        {   //拼接每一个转换后的数字
            if(i+1>=len)
               senddata.append(两HEX字符转1字节数('0',hexStr[i].toLatin1()));
            else
               senddata.append(两HEX字符转1字节数(hexStr[i].toLatin1(),hexStr[i+1].toLatin1()));
         }
    }
    return senddata;
}

//将2个字符转换为hex，//00-FF -> 0-255
quint8 两HEX字符转1字节数(quint8 a,quint8 b)
{
    quint8 c;
    if((a >= '0') && (a <= '9'))
        c= a - 0x30;
    else if((a >= 'A') && (a <= 'F'))
        c= a - 'A' + 10;//'A' = 65;
    else if((a >= 'a') && (a <= 'f'))
        c= a - 'a' + 10;//'a' = 97;
    else
        c= 0;

    c*=16;//前面字符需要进位

    if((b >= '0') && (b <= '9'))
        c+= b - 0x30;
    else if((b >= 'A') && (b <= 'F'))
        c+= b - 'A' + 10;//'A' = 65;
    else if((b >= 'a') && (b <= 'f'))
        c+= b - 'a' + 10;//'a' = 97;
    else
        c+= 0;

    return c;
}
QString 两HexQStr字符ASCII(QString 两字符)
{

    //toUInt();只能转0~9的字符，abcdef的字符转不了，需用以下格式
    bool ok;
    quint8 a=两字符.mid(0,1).toUInt(&ok,16);
    quint8 b=两字符.mid(1,1).toUInt(&ok,16);
    quint8 c;
    c=a*16+b;


    QByteArray aa;
    aa.append(c);
//    qDebug()<<"在QByteArrr：C"<<c;
//    qDebug()<<"在QByteArray："<<aa;
    QTextCodec *codec = QTextCodec::codecForName("UTF-8");
    QString ASCII=codec->toUnicode(aa);
    //qDebug()<<"ASCII码在"<<ASCII;
    return ASCII;
}

QString ERR码解析(QString 两字符)
{
    QString 错误内容;
    quint8 a=两字符.mid(0,1).toUInt();
    quint8 b=两字符.mid(1,1).toUInt();
    quint8 c;
    c=a*16+b;

    // 7   6   5     4             3          2           1        0
    //保留 保留 保留 远程控制失败 通信速率不能更改 密码错/未授权 无请求数据项 数据非法

    quint8 d;
    QStringList 解析方式;
    for(int i=8;i>0;i--)
    {
        d =c & (128>>i);//每右移一次就是除以2
        qDebug()<<"错误内容个2^i"<<d;
        qDebug()<<"错误内容个代号"<<QString::number(d,10);
        if(d!=0)
        {  解析方式=Read_CONFIG_INI(DL645解析方式,"ERRcode",QString::number(d,10));
           错误内容+=解析方式[0];
        }
    }

    return 错误内容;
}




//字节数组换成为00~FF的16进制多字节字符串,转换后主要用于在UI中显示
QString QBtAy转HEX字符串(QByteArray Str,int 是否有空格)
{
    QString readdata;
    QString temp;
    int len = Str.length();
    int h,l;

    for(int i = 0; i < len; i++ )
    {
        h=quint8(Str[i])/16;//QByteArray保存的是有符号数，需转成无符号
        l=quint8(Str[i])%16;
        switch(h){
            case 0:temp='0';break;
            case 1:temp='1';break;
            case 2:temp='2';break;
            case 3:temp='3';break;
            case 4:temp='4';break;
            case 5:temp='5';break;
            case 6:temp='6';break;
            case 7:temp='7';break;
            case 8:temp='8';break;
            case 9:temp='9';break;
            case 10:temp='A';break;
            case 11:temp='B';break;
            case 12:temp='C';break;
            case 13:temp='D';break;
            case 14:temp='E';break;
            case 15:temp='F';break;
        default:temp='g';
        }
        switch(l){
            case 0:temp+='0';break;
            case 1:temp+='1';break;
            case 2:temp+='2';break;
            case 3:temp+='3';break;
            case 4:temp+='4';break;
            case 5:temp+='5';break;
            case 6:temp+='6';break;
            case 7:temp+='7';break;
            case 8:temp+='8';break;
            case 9:temp+='9';break;
            case 10:temp+='A';break;
            case 11:temp+='B';break;
            case 12:temp+='C';break;
            case 13:temp+='D';break;
            case 14:temp+='E';break;
            case 15:temp+='F';break;
        default:temp='g';
        }

        //拼接每一个转换后的字符
        if(是否有空格){ readdata += temp+" ";}//字节间加空格
        else { readdata += temp;}//字节间不加空格
    }
    return readdata;
}



//每个数加33H并逆序
QByteArray 加33H(QString hexStr)
{
    hexStr=过滤非HEX字符(hexStr);
    QByteArray arr=HEX字符串转HEX数组(hexStr);
    QByteArray array;

    for(int i=0;i<arr.length();i++)
    {
        array.prepend(arr[i]+0x33);//每次加到最前面，就可以逆序了
    }
    return array;
}

//每个数33H并逆序--未验证
QByteArray 减33H(QByteArray hexByt)
{
    //hexStr=过滤非HEX字符(hexStr);
   // QByteArray arr=HEX字符串转HEX数组(hexStr);
    QByteArray array;

    for(int i=0;i<hexByt.length();i++)
    {
        array.prepend(hexByt[i]-0x33);//每次加到最前面，就可以逆序了
    }
    return array;
}

//读配置文件,   ini文件地址，节名，键名
QStringList Read_CONFIG_INI(QString iniFile, QString section, QString key)
{
    //分配打开Config.ini文件的内存
    QSettings Config_ini_Read(iniFile , QSettings::IniFormat);
    Config_ini_Read.setIniCodec("UTF-8"); // 让 ini 支持中文

    QStringList value;
    QString 具体的键名 = "/" + section + "/" + key ;

    QVariant 确定有值=Config_ini_Read.value(具体的键名);
//    qDebug()<<"字符"<<确定有值;
//    qDebug()<<"字符"<<!确定有值.isNull();
//    qDebug()<<"字符"<<!确定有值.isValid();
    if(!确定有值.isNull()){value = 确定有值.toStringList();}
    else{value.append("错误！没有对应的键");}
    //qDebug()<<"字符"<<value;
    return value;
}

//写配置文件
int Write_CONFIG_INI(QString iniFile, QString section, QString key, QStringList value)
{
    //分配打开Config.ini文件的内存
    QSettings  Config_ini_Write(iniFile, QSettings::IniFormat); ;
    Config_ini_Write.setIniCodec("UTF-8"); // 可以 ini 支持中文，但是无法支持中文节名和键名

    QString 具体的键名 = "/" + section + "/" + key ;
   // qDebug() << "具体的键名"<<具体的键名 ;
    Config_ini_Write.setValue(具体的键名,value);

    return 0;
}

//根据控制码和数据内容，拼接成帧并校验
QByteArray 生成DL645帧(QString 地址, QString 控制码,QString *长度Str,QString 标识符,QString 密码,QString 操作者代码,QString DATA区,QString 帧序号)
{
    QByteArray 一区,二区,三区,四区,五区,六区,七区,八区;
    一区=只逆序(地址);
    二区=只逆序(控制码);
    //三区=只逆序(ui->lineE_3length->text());
    四区=加33H(标识符);
    五区=加33H(密码);
    六区=加33H(操作者代码);
    七区=加33H(DATA区);
    八区=加33H(帧序号);

    QList<int> 拼接模式=DL645拼接模式(控制码);//根据控制码，得到拼接模式

    //先算出长度
    quint8 长度=0;//长度只计算四区到八区的字节个数。
    if(拼接模式[0]) 长度+=四区.length();
    if(拼接模式[1]) 长度+=五区.length();
    if(拼接模式[2]) 长度+=六区.length();
    if(拼接模式[3]) 长度+=七区.length();
    if(拼接模式[4]) 长度+=八区.length();

    *长度Str=QString::number(长度,16);//*长度Str用来传递给用户显示界面
    三区=只逆序(*长度Str);


    //拼接出需校验区
    QByteArray 需校验区;
    需校验区.append(0x68);
    需校验区.append(一区);//地址
    需校验区.append(0x68);
    需校验区.append(二区);//控制码
    需校验区.append(三区);//长度
    if(拼接模式[0]) 需校验区.append(四区);//标识符
    if(拼接模式[1]) 需校验区.append(五区);//密码
    if(拼接模式[2]) 需校验区.append(六区);//操作者代码
    if(拼接模式[3]) 需校验区.append(七区);//DATA区
    if(拼接模式[4]) 需校验区.append(八区);//帧序号

    //校验方式是区内的每个数累加，结果为0到255以内的数
    quint8 CS=0;
    for(quint8 i=0;i<需校验区.length();i++)
    {
        CS+=需校验区.data()[i];
    }

    //拼接成最终的发送帧
    QByteArray 待发送;
    待发送.resize(4);
    待发送[0]=0xFE;
    待发送[1]=0xFE;
    待发送[2]=0xFE;
    待发送[3]=0xFE;
    待发送.append(需校验区);
    待发送.append(CS);//校验码
    待发送.append(0x16);//结束符16H

    qDebug()<<"开始CS"<<CS;
    qDebug()<<"开始待发送"<< 待发送;

  //  qDebug()<<"需校验区"<<需校验区.toHex();
   // qDebug()<<"待发送"<<待发送.toHex();

    return 待发送;
}

//根据控制码，生成645的帧的拼接模式
QList<int> DL645拼接模式(QString 控制码)
{
    QList<int> 拼接模式;
    if(控制码=="11")//主站发，读数据
    {
       return 拼接模式<<1<<0<<0<<0<<0;
    }

    else if(控制码=="91"||控制码=="B1")//从站回，读数据
    {
       return 拼接模式<<1<<0<<0<<1<<0;
    }

    else if(控制码=="12")//主站发，读后续数据
    {
       return 拼接模式<<1<<0<<0<<0<<1;
    }

    else if(控制码=="92"||控制码=="B2")//从站回，读后续数据
    {
       return 拼接模式<<1<<0<<0<<1<<1;
    }

    else if(控制码=="18")//主站发，修改密码
    {
       return 拼接模式<<1<<1<<0<<1<<0;
    }

    else if(控制码=="1B")//主站发，事件清零
    {
       return 拼接模式<<0<<1<<1<<1<<0;
    }

    else if(控制码=="14"||控制码=="1C")//14主站发，写数据。1C主站发，控制指令。
    {
       return 拼接模式<<1<<1<<1<<1<<0;
    }

    //主要是错误码，只有一个DATA区域
    else if(控制码=="D1"||控制码=="D2"||控制码=="D4"||控制码=="D5"||
            控制码=="D7"||控制码=="D8"||控制码=="DB"||控制码=="DC"||
            控制码=="17"||控制码=="97"||控制码=="93"||控制码=="15"||
            控制码=="08"||控制码=="98"||控制码=="99")
    {
       return 拼接模式<<0<<0<<0<<1<<0;
    }

    //主要是从站正常应答，没有数据区，长度为0
    else if(控制码=="13"||控制码=="19"||控制码=="1A"||控制码=="94"||
            控制码=="95"||控制码=="9B"||控制码=="9C")
    {
       return 拼接模式<<0<<0<<0<<0<<0;
    }

    else {
//        ui->lineE_4flag->setEnabled(true);
//        ui->lineE_5passwd->setEnabled(true);
//        ui->lineE_6operator->setEnabled(true);
//        ui->lineE_7dataArea->setEnabled(true);
//        ui->lineE_SEQ->setEnabled(true);
        拼接模式<<1<<1<<1<<1<<1;
    }

    return 拼接模式;
}

//FE先导字节   68  6字节地址          68H 11控制符 04数据区长度  CC校验码  16结束符
//FE FE FE FE 68 06 05 04 03 02 01 68 11 04 34 34 33 37 CC 16
//验证有没有FE开头，68H, 帧长度，校验码，16结束符
QString 验证接收帧(QByteArray 接收帧,QString *地址,QString *减33H后)
{
    QString 解帧结果;
    QByteArray DATA总区;


    if(接收帧.length()<12)//接收帧长度小于12，帧一定是错的。
      { return  解帧结果="帧错误！帧总长度不应小于12";;}

    int i=0;
    for(;i<5;i++)
    {
        if(int(接收帧[i])==0x68) break;
    }
//    qDebug()<<"接收帧13时校验码= "<<quint8(接收帧[14]);
    qDebug()<<"接收帧= "<<接收帧;


    //i和i+7的值应是68H
    if(int(接收帧[i])!=0x68 || int(接收帧[i+7])!=0x68)//如果i和i+7有一个不是68H，指令错误
      { return  解帧结果="帧错误！帧起始符应是68H";}

     DATA总区=接收帧.mid(i+1,6);//先用来临时存放样机地址
     std::reverse(DATA总区.begin(), DATA总区.end());//QByteArray逆序
     *地址=QBtAy转HEX字符串(DATA总区,1);


    //接收帧.length()-i-12是有效的数据区长度
    if(int(接收帧[i+9])!=(接收帧.length()-i-12))//检查长度与帧长度是否一致
      {return 解帧结果="帧错误！帧长度与描述不一致,或无控制码";}

    //结束符应是16H
    if(quint8(接收帧[接收帧.length()-1])!=0x16)
      {return  解帧结果="帧错误！结束符应是16H";}

    //接收帧.length()-i-2就是参与校验码的长度
    //qDebug()<<"参与校验码的长度"<<接收帧.length()-i-2;
    quint8 CS=0;
    for(int j=0;j<接收帧.length()-i-2;j++)
    {
        CS+=接收帧[j+i];
    }
   // qDebug("重算的CS %x",CS);
    //QByteArray会有大于7f会读多的问题，比如df读成fffffdf，所以要加quint8()
    if(CS!=quint8(接收帧[接收帧.length()-2]))
      {return  解帧结果="帧错误！校验码错误";}
   // qDebug("校验码相同 %x",接收帧.data()[接收帧.length()-2]);

    //i+10是开始截的位置。
    //接收帧[i+9]是要截取的长度，由帧内的L得知
    DATA总区=接收帧.mid(i+10,接收帧[i+9]);
    DATA总区=减33H(DATA总区);

    //取出控制码，生成拼接模式
    QByteArray aaa;//将QByteArray的某个元素转为QString太麻烦了
    aaa.append(接收帧[i+8]);
    QString 控制码=QBtAy转HEX字符串(aaa,0);
    QList<int> 拼接模式= DL645拼接模式(控制码);

    //qDebug()<<"控制码1111"<<控制码;
    //qDebug()<<"拼接模式1111"<<拼接模式;


    //根据拼接模式，分离DATA总区的各子项
    QString 帧内容=QBtAy转HEX字符串(DATA总区,0);
    QString 标识符,密码,操作者代码,DATA区,帧序号;
    int L=帧内容.length();;
    int K=0;//移动要截取的位置
    if(拼接模式[0]) {K+=8;     标识符=帧内容.mid(L-K,8);}
    if(拼接模式[1]) {K+=8;       密码=帧内容.mid(L-K,8);}
    if(拼接模式[2]) {K+=8;  操作者代码=帧内容.mid(L-K,8);}
    if(拼接模式[4]) {          帧序号=帧内容.mid(0,2);}//有个问题，如果回来的《后续帧》没带帧序号，这程序是检测不出来的
    if(拼接模式[3])
    {
        if(拼接模式[4])  DATA区=帧内容.mid(2,L-K-2);
        else DATA区=帧内容.mid(0,L-K);
    }



    解帧结果=解释帧含义(控制码,标识符,密码,操作者代码,DATA区,帧序号);
    *减33H后=控制码+" "+标识符+" "+密码+" "+操作者代码+" "+DATA区+" "+帧序号;


//   qDebug()<<"标识符1111 "<<标识符;
//   qDebug()<<"标识符1111 "<<密码;
//   qDebug()<<"标识符1111 "<<操作者代码;
//   qDebug()<<"标识符1111 "<<DATA区;
//   qDebug()<<"标识符1111 "<<帧序号;

   //QString 节=标识符.mid(0,4);
  // QString 键=标识符.mid(4,4);
  // QStringList 解析帧=Read_CONFIG_INI(DL645解析方式,节, 键);

   //qDebug()<<"解析帧1111"<<解析帧;
    //解帧结果=QBtAy转HEX字符串(DATA总区,1);

    return 解帧结果;
}

//根据拼接模式，分离DATA总区的各子项
//          标识符,       密码,        操作者代码,    DATA区,
//      34 34 33 37  3C 3A 38 36  37 36 35 34  77 66 55 44   帧序号
//帧序号 11 22 33 44  01 02 03 04  03 05 07 09  04 00 01 01
//          DATA区,     操作者代码,      密码,        标识符,
//帧序号为1字节，DATA区不定长，其余均为4字节

QString 通用91码解帧方式(QStringList 解析方式,QString DATA区)
{
    QString 解帧结果;
    int k=0;//k用来移动接收帧的位置，一个字节移动两节
    QStringList 查表;
    bool ok;
    for(int i=0;i<解析方式.length();i++)
    {
        if(解析方式[i]=="AA"){ 解帧结果+=DATA区.mid(k,2);  k+=2; continue;}//直接转文本显示
        if(解析方式[i]=="xx"){ 解帧结果+=DATA区.mid(k,1)+"."+DATA区.mid(k+1,1);  k+=2; continue;}//加小数点
        if(解析方式[i]=="AS")//转成16进制数值对应的ASCII码
        {
            解帧结果+=两HexQStr字符ASCII(DATA区.mid(k,2));
            k+=2;continue;
        }
        if(解析方式[i]=="F9")//第一位小于8是正数，大于等于8是负数
        {
            qint8 第一位数=DATA区.mid(k,1).toUInt(&ok,16);

            qDebug()<<"第一位数"<<第一位数;

            if(第一位数<8)
            { 解帧结果+=" "+DATA区.mid(k,2);  k+=2; continue;}//直接转文本显示，前面加空格，不用+号，因与-区分度不高
            else
            {

                第一位数=第一位数-8;
                qDebug()<<"第一位数"<<第一位数;
                解帧结果+="-"+QString::number(第一位数,10);
                k++;
                解帧结果+=DATA区.mid(k,1);
                k++;
            }
            continue;
        }
        //超过799999.99就是负数。
        //899999.99=-99999.99；F99999.99=-799999.99
        //800000.00和000000.00都是0。

        if(解析方式[i]=="bin")
        {
            i++;
            查表=Read_CONFIG_INI(DL645解析方式,解析方式[i],DATA区.mid(k,2));
            解帧结果+=查表[0];
            k+=2; continue;
        }

/*************bit类型************************************************************开始****/
        if(解析方式[i]=="bit"){ //主要是 运行状态字1、运行状态字2、控制字1到6
            i++;
            quint8 a=DATA区.mid(k,2).toUInt(&ok,16);

      /*************运行状态字1********************/
            if(解析方式[i]=="runState1"){//运行状态字1
                k+=2;
                int 告警状态=a & 0x80;
                int 闸位状态=a & 0x60;
                int 跳闸告警原因=a &0x1F;
                if(告警状态 ) {解帧结果+="有告警，";}
                else{解帧结果+="无告警，";}

                switch(闸位状态){
                    case 0x00: 解帧结果+="合闸，";break;
                    case 0x20: 解帧结果+="分闸，";break;
                    case 0x40: 解帧结果+="重合闸，";break;
                    case 0x60: 解帧结果+="闭锁跳闸，";break;
                  default: 解帧结果+="闸位状态读取错误，";
                }
                QStringList 查表=Read_CONFIG_INI(DL645解析方式,"runState1",QString::number(跳闸告警原因,16));
                解帧结果+=查表[0];
                continue;
            }
       /*************运行状态字1********************/

       /*************运行状态字2********************/
            if(解析方式[i]=="runState2"){//运行状态字2
                k+=2;
                int 跳闸次数=a & 0x07;
                int 状态2告警=a &0xF8;
                查表=Read_CONFIG_INI(DL645解析方式,"runState2",QString::number(状态2告警,16));
                解帧结果+=查表[0];
                解帧结果+="跳闸次数："+QString::number(跳闸次数,10)+"；";
                continue;
            }
      /*************运行状态字2********************/

      /*************运行状态字3、4********************/
            if(解析方式[i]=="runState3"||解析方式[i]=="runState4"){//运行状态字3和4
                quint8 电能类型=DATA区.mid(k,2).toUInt(&ok,16);
                k+=2;

                quint8 a;
                for(int b=8,比较位=0x80;b>0;b--)
                {
                   a =电能类型 & 比较位;//每右移一次就是除以2, 键采用80 40 20 10 8 4 2 1的方式表示
                   查表=Read_CONFIG_INI(DL645解析方式,解析方式[i],QString::number(比较位,16));
                   比较位=比较位>>1;
                   if(查表[0]=="保留") continue;//查表得到的值是 “保留” 则不显示。
                   //0正向，1反向
                   if(a){解帧结果+=查表[0]+"反向；";}
                   else {解帧结果+=查表[0]+"正向；";}
                }
                continue;
            }
      /*************运行状态字3、4********************/

      /*************运行状态字5的类型太多，直接用代码解析了，不用ini*****************/
            if(解析方式[i]=="runState5"){//运行状态字5

                quint8 状态类型=DATA区.mid(k,2).toUInt(&ok,16);
                k+=2;

                quint8 a;
                int 比较位=0x80;
                a =状态类型 & 比较位;
                if(a){解帧结果+="错误！！！bit7应无效；";}

                比较位=0x40;
                a =状态类型 & 比较位;
                if(a){解帧结果+="错误！！！bit6应无效；";}

                比较位=0x20;
                a =状态类型 & 比较位;
                if(a){解帧结果+="当前运行时区第二套；";}
                else {解帧结果+="当前运行时区第一套；";}

                比较位=0x10;
                a =状态类型 & 比较位;
                if(a){解帧结果+="继电器状态：断开；";}
                else {解帧结果+="继电器状态：导通；";}

                比较位=0x08;
                a =状态类型 & 比较位;
                if(a){解帧结果+="编程允许：允许；";}
                else {解帧结果+="编程允许：禁止；";}

                比较位=0x06;
                a =状态类型 & 比较位;
                if(a==0){解帧结果+="供电方式：主电源；";}
                if(a==2){解帧结果+="供电方式：辅助电源；";}
                if(a==4){解帧结果+="供电方式：电池供电；";}
                if(a==6){解帧结果+="供电方式：错误！！无此方式；";}

                比较位=0x01;
                a =状态类型 & 比较位;
                if(a){解帧结果+="当前运行时段：第二套；";}
                else {解帧结果+="当前运行时段：第一套；";}

                continue;
            }
      /*************运行状态字5********************/

      /*************控制字1、2、3、5、6、7********************/
            if(解析方式[i]=="control_1"||解析方式[i]=="control_2"||解析方式[i]=="control_3"||
               解析方式[i]=="control_5"||解析方式[i]=="control_6"||解析方式[i]=="control_7")
               {
                quint8 告警类型=DATA区.mid(k,2).toUInt(&ok,16);
                k+=2;

//                qDebug()<<"运行状态1111"<<查表;
//                qDebug()<<"跳闸告警原因"<<QString::number(状态2告警,16);
                qDebug()<<"告警类型1111"<<告警类型;
                quint8 a;
                for(int b=8,比较位=0x80;b>0;b--)
                {
                    a =告警类型 & 比较位;//每右移一次就是除以2, 键采用80 40 20 10 8 4 2 1的方式表示

                    查表=Read_CONFIG_INI(DL645解析方式,解析方式[i],QString::number(比较位,16));
                    比较位=比较位>>1;
                    if(查表[0]=="保留") continue;//查表得到的值是 “保留” 则不显示。
                    //1允许，0禁止
                    if(a){解帧结果+=查表[0]+"允许；";}
                    else {解帧结果+=查表[0]+"禁止；";}
                }
                continue;
            }
       /*************控制字1、2、3、5、6、7********************/

       /*************控制字4******************************/
            if(解析方式[i]=="control_4")
            {
                quint8 告警类型=DATA区.mid(k,2).toUInt(&ok,16);
                k+=2;
                quint8 a;

                a =告警类型 & 0xF0;
                查表=Read_CONFIG_INI(DL645解析方式,"control_4_74",QString::number(a,16));
                解帧结果+="额定剩余电流动作值："+查表[0];

                a =告警类型 & 0x0C;
                查表=Read_CONFIG_INI(DL645解析方式,"control_4_32",QString::number(a,16));
                解帧结果+="额定极限不驱动时间："+查表[0];

                a =告警类型 & 0x03;
                查表=Read_CONFIG_INI(DL645解析方式,"control_4_10",QString::number(a,16));
                解帧结果+="剩余电流报警时间："+查表[0];

                continue;
            }
       /*************控制字4******************************/
        }

       /*************有功、无功组合方式特征字********************/
              if(解析方式[i]=="activeCombination"||解析方式[i]=="reactiveCombination1"||解析方式[i]=="reactiveCombination2"){
                  quint8 电能类型=DATA区.mid(k,2).toUInt(&ok,16);
                  k+=2;

                  quint8 a;
                  for(int b=8,比较位=0x80;b>0;b--)
                  {
                     a =电能类型 & 比较位;//每右移一次就是除以2, 键采用80 40 20 10 8 4 2 1的方式表示
                     查表=Read_CONFIG_INI(DL645解析方式,解析方式[i],QString::number(比较位,16));
                     比较位=比较位>>1;
                     if(查表[0]=="保留") continue;//查表得到的值是 “保留” 则不显示。
                     //0正向，1反向
                     if(b%2)
                     {
                         if(a){解帧结果+="加 "+查表[0]+"；";}
                         //else {解帧结果+=查表[0]+"不加；";}//显示不加、不减、没有意义，所以不显示
                     }
                     else{
                         if(a){解帧结果+="减 "+查表[0]+"；";}
                         //else {解帧结果+=查表[0]+"不减；";}//显示不加、不减、没有意义，所以不显示
                     }
                  }
                  continue;
              }
        /*************有功、无功组合方式特征字********************/

/*************bit类型************************************************************开始****/

        if(解析方式[i]=="换行"){解帧结果+="\n";continue;}//只适用于99控制码

        else {解帧结果+=解析方式[i];}
    }
    if(DATA区.length()> k )解帧结果+="，帧比描述长，数据 "+DATA区.right(DATA区.length()-k)+" 未匹配；";
    if(DATA区.length()< k) 解帧结果+="，帧长度小于描述；";
    return 解帧结果;
}


QString 解释帧含义(QString 控制码,QString 标识符,QString 密码,QString 操作者代码,QString DATA区,QString 帧序号)
{
    QString 解帧结果;
    QString 节=标识符.mid(0,4);
    QString 键=标识符.mid(4,4);

    QStringList 解析方式=Read_CONFIG_INI(DL645解析方式,节,键);
    qDebug()<<"DATA区："<<DATA区;

    if(控制码=="11"){ 解帧结果="11码, 主站查询从机"+解析方式[0]; return 解帧结果;}
    qDebug()<<"控制码是什么"<<控制码;

/**************************91编码，从机返回************************************开始****/
    if(控制码=="91"||控制码=="92"||控制码=="B2")
    {
        if(节=="0382"||节=="0383")//最多事件的两个编码
        {
            bool ok;
            quint16 a=标识符.mid(6,2).toUInt(&ok,16);
            键=标识符.mid(4,2)+"xx";
            解析方式=Read_CONFIG_INI(DL645解析方式,节,键);
            if(a==1) {解帧结果="今天内"+通用91码解帧方式(解析方式,DATA区);}
            else {a--; 解帧结果="前第 "+QString::number(a,10)+"天当天，"+通用91码解帧方式(解析方式,DATA区);}

            if(控制码=="92"||控制码=="B2"){return 解帧结果+="帧序号:"+帧序号;}
            return 解帧结果;
        }

        if(节=="0388"||节=="038D"||节=="038E"||节=="038F")//跟"0390"编码大致一样，但这是从1开始作为1的
        {
            bool ok;
            quint16 a=标识符.mid(6,2).toUInt(&ok,16);
            if(标识符.mid(4,2)=="00")
            {
                键=标识符.mid(4,2)+"xx";
                解析方式=Read_CONFIG_INI(DL645解析方式,节,键);
                解帧结果="上"+QString::number(a,10)+"次"+通用91码解帧方式(解析方式,DATA区);
            }
            if(标识符.mid(4,2)=="01")
            {
                a+=257;
                键=标识符.mid(4,2)+"xx";
                解析方式=Read_CONFIG_INI(DL645解析方式,节,键);
                解帧结果="前第"+QString::number(a,10)+"次"+通用91码解帧方式(解析方式,DATA区);
            }
            if(控制码=="92"||控制码=="B2"){return 解帧结果+="帧序号:"+帧序号;}
            return 解帧结果;
        }


        if(节=="0390")//跟"0388"编码大致一样，但这是从0开始作为1的
        {
            bool ok;
            quint16 a=标识符.mid(6,2).toUInt(&ok,16);
            if(标识符.mid(4,2)=="00")
            {
                a++;
                键=标识符.mid(4,2)+"xx";
                解析方式=Read_CONFIG_INI(DL645解析方式,节,键);
                解帧结果="前第"+QString::number(a,10)+"次"+通用91码解帧方式(解析方式,DATA区);
            }
            if(标识符.mid(4,2)=="01")//从257次漏电开始
            {
                a+=257;
                键=标识符.mid(4,2)+"xx";
                解析方式=Read_CONFIG_INI(DL645解析方式,节,键);
                解帧结果="上"+QString::number(a,10)+"次"+通用91码解帧方式(解析方式,DATA区);
            }
            if(控制码=="92"||控制码=="B2"){return 解帧结果+="帧序号:"+帧序号;}
            return 解帧结果;
        }

        if(节=="0401")//第一套第,NO,日时段表数据：
        {
            bool ok;
            quint16 a=标识符.mid(6,2).toUInt(&ok,16);
            if(标识符.mid(4,4)=="0000")
            {
                键="0000";//0000=第一套时区表数据：
                解析方式=Read_CONFIG_INI(DL645解析方式,节,键);
                解帧结果=通用91码解帧方式(解析方式,DATA区);
            }
            if(标识符.mid(6,2)!="00")
            {
                键="00xx";//第一套第,NO,日时段表数据：
                解析方式=Read_CONFIG_INI(DL645解析方式,节,键);
                解帧结果="第一套第"+QString::number(a,10)+"日时段表数据："+通用91码解帧方式(解析方式,DATA区);
            }
            if(控制码=="92"||控制码=="B2"){return 解帧结果+="帧序号:"+帧序号;}
            return 解帧结果;
        }

        if(节=="0402")//第二套第,NO,日时段表数据：
        {
            bool ok;
            quint16 a=标识符.mid(6,2).toUInt(&ok,16);
            if(标识符.mid(4,4)=="0000")
            {
                键="0000";//0000=第二套时区表数据：
                解析方式=Read_CONFIG_INI(DL645解析方式,节,键);
                解帧结果=通用91码解帧方式(解析方式,DATA区);
            }
            if(标识符.mid(6,2)!="00")
            {
                键="00xx";//第二套第,NO,日时段表数据：
                解析方式=Read_CONFIG_INI(DL645解析方式,节,键);
                解帧结果="第二套第"+QString::number(a,10)+"日时段表数据："+通用91码解帧方式(解析方式,DATA区);
            }
            if(控制码=="92"||控制码=="B2"){return 解帧结果+="帧序号:"+帧序号;}
            return 解帧结果;
        }



        if(节=="0403")//假日，有1到254个
        {
            bool ok;
            quint16 a=标识符.mid(6,2).toUInt(&ok,16);
            if(标识符.mid(4,2)=="00")
            {
                键=标识符.mid(4,2)+"xx";
                解析方式=Read_CONFIG_INI(DL645解析方式,节,键);
                解帧结果="第"+QString::number(a,10)+"公共假日是："+通用91码解帧方式(解析方式,DATA区);
            }
            if(控制码=="92"||控制码=="B2"){return 解帧结果+="帧序号:"+帧序号;}
            return 解帧结果;
        }


        //一般的情况
       else
       {
            解帧结果=通用91码解帧方式(解析方式,DATA区);
            if(控制码=="92"||控制码=="B2"){return 解帧结果+="帧序号:"+帧序号;}
            return 解帧结果;
       }
    }
/**************************91编码，从机返回************************************结束****/
    if(控制码=="12"){ 解帧结果="12码, 主站查询从机后续数据"+解析方式[0]; return 解帧结果;}


/***********1B编码主机要求从机清零，9B从机回应已清零*******************************开始****/
    if(控制码=="1B")
    {  if(DATA区=="FFFFFFFF"){解帧结果="1B码, 从机数据全清零，密码："+密码+"，操作者代码："+操作者代码; }
       else{
            DATA区="03"+DATA区.right(6);
            //qDebug()<<"清零项："<<DATA区;
            节=DATA区.mid(0,4);
            键=DATA区.mid(4,4);
            解析方式=Read_CONFIG_INI(DL645解析方式,节,键);//这4行代码是将FF替换成03，然后才能去查ini文件

            解帧结果="1B码, 分项数据清零，密码："+密码+"，操作者："+操作者代码+"，清零项："+解析方式[0];
        }//后续要加帧序号
        return 解帧结果;

    }
    if(控制码=="9B"){ 解帧结果="9B码, 数据已清零";return 解帧结果;}
/***********1B编码主机要求从机清零，9B从机回应已清零*******************************结束****/

/***********D1 D2 D4 D5 D7 D8 DB DC 回应错误类型*******************************开始****/
    if(控制码=="D1"||控制码=="D2"||控制码=="D4"||控制码=="D5"||
       控制码=="D7"||控制码=="D8"||控制码=="DB"||控制码=="DC")
    {
        // 7   6   5     4             3          2           1        0
        //保留 保留 保留 远程控制失败 通信速率不能更改 密码错/未授权 无请求数据项 数据非法
        bool ok;
        quint8 a=DATA区.mid(0,2).toUInt(&ok,16);
        //quint8 b=DATA区.mid(1,1).toUInt(&ok,16);
        //quint8 c=a*16+b;
        quint8 d;
        for(int i=8;i>0;i--)
        {
            d =a & (128>>i);//每右移一次就是除以2, 键采用80 40 20 10 8 4 2 1的方式表示
            if(d!=0)
            {  解析方式=Read_CONFIG_INI(DL645解析方式,"errBIT",QString::number(d,16));
               解帧结果+=解析方式[0];
            }
        }
        解帧结果="错误码 "+控制码+" "+DATA区+" "+解帧结果;
        return 解帧结果;
    }
/***********D1 D2 D4 D5 D7 D8 DB DC 回应错误类型*******************************结束****/


/***********93 13 95 15 19 1A 9C 98 94***************************************开始****/
    if(控制码=="93"){ 解帧结果="93码, 从机地址为："+DATA区;return 解帧结果;}
    if(控制码=="13"){ 解帧结果="13码, 主站查询从机地址";return 解帧结果;}

    if(控制码=="95"){ 解帧结果="95码, 从机地址正常更改";return 解帧结果;}
    if(控制码=="15"){ 解帧结果="15码, 设置从机地址为："+DATA区;return 解帧结果;}

    if(控制码=="19"){ 解帧结果="19码, 主站对从站告警的查询请求帧";return 解帧结果;}
    if(控制码=="1A"){ 解帧结果="1A码, 主站对异常告警的应答帧"; return 解帧结果;}

    if(控制码=="94"){ 解帧结果="94码, 主站写从机数据，从机正常应答";return 解帧结果;}
/***********93 13 95 15 19 1A 9C 98 94***************************************结束****/

/***********17 97 更改波特率**************************************************开始****/
    if(控制码=="17"){
        解析方式=Read_CONFIG_INI(DL645解析方式,"BaudRate",DATA区);//此时DATA区是一个字节
        解帧结果="17码, 更改从机波特率为："+解析方式[0];
        qDebug()<<"17码, 更改从机波特率为："<<DATA区;
        return 解帧结果;
    }
    if(控制码=="97"){
        解析方式=Read_CONFIG_INI(DL645解析方式,"BaudRate",DATA区);//此时DATA区是一个字节
        解帧结果="97码, 从机已更改波特率为："+解析方式[0];
        qDebug()<<"97码, 从机已更改波特率为："<<DATA区;
        return 解帧结果;
    }
/***********17 97 更改波特率**************************************************结束****/

/***********99码，从机自动发送到从机的异常***************************************开始****/
    if(控制码=="99"){
        解析方式=Read_CONFIG_INI(DL645解析方式,"99S","99K");//此时DATA区是一个字节
        return 解帧结果=通用91码解帧方式(解析方式,DATA区);
    }
/***********99码，从机自动发送到从机的异常***************************************结束****/

/***********08码，广播校时****************************************************开始****/
    if(控制码=="08"){
        if(DATA区.length()==12)
        {解帧结果="广播校时："+DATA区[0]+""+DATA区[1]+"年"+DATA区[2]+""+DATA区[3]+"月"+DATA区[4]+""+DATA区[5]+"日"
                           +DATA区[6]+""+DATA区[7]+"时"+DATA区[8]+""+DATA区[9]+"分"+DATA区[10]+""+DATA区[11]+"秒";
        }
        else 解帧结果="广播校时格式错误";
        return 解帧结果;
    }
    //解帧结果=DATA区[0]+DATA区[1];这样会报错
    //解帧结果=DATA区[0]+"年"+DATA区[1];这样不会报错
/***********08码，广播校时****************************************************结束****/

/***********1C码，主站向从站请求动作控制*****************************************开始****/
    if(控制码=="14"||控制码=="1C"){
        if(控制码=="14")解帧结果=QString("14写数据指令，%1%2%3%4%5").arg("密码：").arg(密码).arg("，操作者：").arg(操作者代码).arg("，");
        else 解帧结果=QString("1C控制指令，%1%2%3%4%5").arg("密码：").arg(密码).arg("，操作者：").arg(操作者代码).arg("，");
        qDebug()<<"密码："<<DATA区;
        qDebug()<<"密码："<<DATA区.mid(7,8);
        qDebug()<<"操作者："<<DATA区.mid(15,8);
        int 右边长度=DATA区.length()-24;
        DATA区=DATA区.mid(0,8)+DATA区.mid(23,右边长度);
        解帧结果+=通用91码解帧方式(解析方式,DATA区);
        return 解帧结果;
    }
    if(控制码=="9C"){ 解帧结果="9C码, 主站控制从机，从机正常应答"; return 解帧结果;}
/***********1C码，主站向从站请求动作控制*****************************************结束****/

/***********18码，修改从机密码*************************************************开始****/
    if(控制码=="18"){
        解帧结果=QString("原密码：%1%2%3%4%5").arg(标识符).arg("，新密码：").arg(密码).arg("，权限：").arg(DATA区);
        return 解帧结果;
    }
    if(控制码=="98"){ 解帧结果="98码, 从机新密码为："+DATA区;    return 解帧结果;}
/***********18码，修改从机密码*************************************************开始****/

    if(控制码.isEmpty()){return 解帧结果="没有控制码";}//好像用不到
    else{解帧结果="未定义编码 "+控制码;}



    return 解帧结果;
}




